﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Linq.Expressions;
using System.Xml.Serialization;
using System.Runtime.Serialization;

namespace Kooboo.HtmlFunc
{
    #region ConstructorInvoker
    class ConstructorInvoker
    {
        private Func<object[], object> m_invoker;

        public ConstructorInfo ConstructorInfo { get; private set; }

        public ConstructorInvoker(ConstructorInfo constructorInfo)
        {
            this.ConstructorInfo = constructorInfo;
            this.m_invoker = InitializeInvoker(constructorInfo);
        }

        private Func<object[], object> InitializeInvoker(ConstructorInfo constructorInfo)
        {
            // Target: (object)new T((T0)parameters[0], (T1)parameters[1], ...)

            // parameters to execute
            var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

            // build parameter list
            var parameterExpressions = new List<Expression>();
            var paramInfos = constructorInfo.GetParameters();
            for (int i = 0; i < paramInfos.Length; i++)
            {
                // (Ti)parameters[i]
                var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
                var valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);

                parameterExpressions.Add(valueCast);
            }

            // new T((T0)parameters[0], (T1)parameters[1], ...)
            var instanceCreate = Expression.New(constructorInfo, parameterExpressions);

            // (object)new T((T0)parameters[0], (T1)parameters[1], ...)
            var instanceCreateCast = Expression.Convert(instanceCreate, typeof(object));

            var lambda = Expression.Lambda<Func<object[], object>>(instanceCreateCast, parametersParameter);

            return lambda.Compile();
        }

        public object Invoke(params object[] parameters)
        {
            return this.m_invoker(parameters);
        }
    }
    #endregion

    public class RuntimeKit
    {
        #region Reflection

        static object cacheLock = new object();
        static Dictionary<ConstructorInfo, ConstructorInvoker> InvokerCache;

        static RuntimeKit()
        {
            InvokerCache = new Dictionary<ConstructorInfo, ConstructorInvoker>();
        }

        private static ConstructorInvoker GetInvoker(ConstructorInfo constructor)
        {
            ConstructorInvoker invoker = null;

            if (InvokerCache.TryGetValue(constructor, out invoker))
            {
                return invoker;
            }

            lock (cacheLock)
            {
                if (!InvokerCache.TryGetValue(constructor, out invoker))
                {
                    invoker = new ConstructorInvoker(constructor);
                    InvokerCache[constructor] = invoker;
                }
            }

            return invoker;
        }

        public static object CreateInstance(Type instanceType, params object[] parameters)
        {
            ConstructorInfo constructor = null;

            var constructors = instanceType.GetConstructors();
            foreach (var ctor in constructors)
            {
                var count = ctor.GetParameters().Length;
                if (count == parameters.Length)
                {
                    constructor = ctor;
                    break;
                }
            }

            if (constructor == null) { return null; }

            var invoker = GetInvoker(constructor);
            return invoker.Invoke(parameters);
        }

        public static bool IsInherited(Type target, Type ancestor)
        {
            if (target == null || ancestor == null)
            {
                return false;
            }

            return target.IsSubclassOf(ancestor) || target.GetInterfaces().Contains(ancestor);
        }

        #endregion

        #region Serialization

        public static void XmlSerialize(string filePath, object obj)
        {
            XmlSerialize(filePath, obj, obj.GetType());
        }

        public static void XmlSerialize(string filePath, object obj, Type type)
        {
            using (var writer = new StreamWriter(filePath))
            {
                var ser = new XmlSerializer(type);
                ser.Serialize(writer, obj);
                writer.Close();
            }
        }

        public static T XmlDeserialize<T>(string filePath)
        {
            return (T)XmlDeserialize(filePath, typeof(T));
        }

        public static T XmlDeserialize<T>(Stream stream)
        {
            return (T)XmlDeserialize(stream, typeof(T));
        }

        public static object XmlDeserialize(string filePath, Type type)
        {
            if (!File.Exists(filePath))
            {
                return null;
            }

            using (var reader = new StreamReader(filePath))
            {
                var ser = new XmlSerializer(type);
                return ser.Deserialize(reader);
            }
        }

        public static object XmlDeserialize(Stream stream, Type type)
        {
            var ser = new XmlSerializer(type);
            return ser.Deserialize(stream);
        }

        public static void DataSerialize<T>(T o, Stream output)
        {
            var ser = new DataContractSerializer(typeof(T));
            ser.WriteObject(output, o);
        }

        #endregion

        public static Stream LoadEmbeddedResource(string name)
        {
            var asm = Assembly.GetCallingAssembly();
            var names = asm.GetManifestResourceNames();
            var @namespace = names.Where(o => o.IgnoreCaseEndsWith("." + name)).First();
            return asm.GetManifestResourceStream(@namespace);
        }
    }
}
